#!/usr/bin/env python
"""
:mod:`pynchon.core.index`
=========================

Provides array objects to store N-dimensional integer indices.
"""

import numpy as np
from entity import Entity_maker, Entity_sequencer


class Index(object):
    """
    Index interface class. This class provides methods specific to 
    N-dimensional indices e.g. 0D - 2, 1D - (3,), 2D - (1, 7).
    
    Provides: 'raw' and 'is_valid' methods and the 'rank' attribute to
    subclasses. 
    """
    rank = 0

    @classmethod
    def is_valid_element(cls, element, metadata):
        """
        Verify if element can be a valid ``Index`` instance.        
        """
        if isinstance(element, basestring):
            raise ValueError("Expected a sequence of ints got: \"%s\"." % \
                             (element,))
        if isinstance(element, int):
            element = (element,)
        for index in element:
            if not isinstance(index, (int, \
                         np.uint, np.uint8, np.uint16, np.uint32, np.uint64, \
                         np.int, np.int8, np.int16, np.int32, np.int64)):
                raise ValueError(\
                "Not all elements of \"%s\" are a integers." % (element,))

    @classmethod
    def is_valid_array(cls, array, metadata):
        rng = metadata.get('range', None)
        if rng is not None:
            for element in array.flat:
                if not np.iterable(element):
                    element = (element,)
                for index in element:
                    if not index in rng:
                        raise ValueError(\
                        "Not all elements of \"%s\" are in range." % (element,))

    @classmethod
    def raw_sequence(cls, rank, dtype, fval, metadata, fast, kwargs, entities):
        cls.is_valid_element(entities, metadata)
        try:
            entities = np.array(entities, dtype=dtype)
        except (ValueError, TypeError):
            raise ValueError("Expected a %sD sequence of ints got: \"%s\"." % \
                             (len(dtype), entities))
        cls.is_valid_array(entities, metadata)
        cls.is_valid_array(fval, metadata)
        return (entities,)

    def set_element(self, array, value):
        """
        Sets new value for the array[self] element. 
        """
        array[self.item()] = value
        
    def resolve_element(self, array):
        """
        Returns the array[self] element.
        """
        return array[self.item()]   
         
    def resolve_elements(self, array):
        """
        Returns entity[self['i0' .. 'iN']] elements.
        """           
        return [array[i] for i in self.item()]
        
    def resolve_entity(self, array):
        """
        Returns the array.entities[self] element.
        """
        return array.get_entity(self.item())         
        
    def resolve_entities(self, array):
        """
        Returns entity.entities[self['i0' .. 'iN']] elements.
        """        
        return [array.entities[i] for i in self.item()]


class IndexVector(Index):
    """
    IndexVector interface class. This class provides methods specific to
    vectors of N-dimensional indices e.g. 0D - [1, 0, ... , -2], 1D - 
    [(3,), (2,), ... (3,)], 2D - [(1, 0), (2, 3), ... , (1, 7)].
    """
    rank = 1

    @classmethod
    def raw_sequence(cls, rank, dtype, fval, metadata, fast, kwargs, entities):
        return Entity_sequencer(cls, dtype, fval, metadata, entities)


class IndexArray(IndexVector):
    """
    IndexArray interface class. This class provides methods specific to
    arrays of N-dimensional indices.
    """
    rank = 2


class IndexCube(IndexArray):
    """
    IndexCube interface class. This class provides methods specific to
    cubes (3D) of N-dimensional indices.
    """
    rank = 3


class IndexHyperCube(IndexCube):
    """
    IndexHyperCube interface class. This class provides methods specific to
    cubes (4D) of N-dimensional indices.
    """
    rank = 4


# create classes
classes = Entity_maker(base_classes=(Index, IndexVector, IndexArray, \
                                       IndexCube, IndexHyperCube),
                        dims=(0, 1, 2, 3, 4),
                        types=('int8', 'int16', 'int32', 'int64',
                                'uint8', 'uint16', 'uint32', 'uint64')
                       )
# add to globals
for cls in classes:
    globals()[cls.__name__] = cls
    
